package feder;

public class SpringPendulum {
	public static boolean DEBUG = false;
	public static final double GRAVITATIONAL_ACCELERATION = 9.81; // graviational
																	// acceleration
																	// in m^2/s
	public static final double DAMPING = 0.20;

	public static interface InitialPosition {
		public double[] getInitialDX(SpringPendulum pendulum);
	}

	public static class InitalPositionEquilibrium implements InitialPosition {
		@Override
		public double[] getInitialDX(SpringPendulum pendulum) {
			double[] dx = new double[pendulum.N + 1];
			for (int i = 0; i < dx.length; i++) {
				double gravForce = pendulum.getGravForce(i);
				dx[i] = gravForce / pendulum.k;
			}
			return dx;
		}
	}

	public static class InitialPositionAdditionalWeight implements InitialPosition {

		@Override
		public double[] getInitialDX(SpringPendulum pendulum) {
			double[] dx = new double[pendulum.N + 1];
			for (int i = 0; i < dx.length; i++) {
				double gravForce = pendulum.getGravForce(i) + pendulum.m * 0.01;
				dx[i] = gravForce / pendulum.k;
			}
			return dx;
		}

	}

	public static interface InitialVelocity {
		public double[] getInitalVelocity(SpringPendulum pendulum);
	}

	public static class InitialVelocityNoVelocity implements InitialVelocity {
		@Override
		public double[] getInitalVelocity(SpringPendulum pendulum) {
			double[] v = new double[pendulum.N + 1];
			for (int i = 0; i < v.length; i++) {
				v[i] = 0.;
			}
			return v;
		}
	}

	// input parameters
	private int N = -1; // number of mass and spring elements
	private double step = -1;; // time between two timesteps /s
	private double finalTime = -1.; // count of timesteps
	private int stepCount = -1;
	private double E = -1; // Elastic modulus /Pa
	private double A = -1; // crosssection /m^2
	private double L0 = -1; // Length with no deformation /m
	private double L_i = -1; // Length of one element
	private double k = -1; // spring constant of one line element
	private double m0 = -1; // mass of the spring /kg
	private double m_i = -1; // mass of one spring element /kg
	private double m = -1; // mass at the bottom /kg

	// initial condition
	private InitialPosition dx0;
	private InitialVelocity v0;

	// trajectories (1st dim: time, 2nd dim: position in spring)
	private double[][] dx;// test
	private double[][] v;

	public SpringPendulum(int N, double step, double finalTime, double E, double A, double L0, double m0, double m) {
		if (N <= 0 || step <= 0 || finalTime <= 0 || E <= 0 || A <= 0 || L0 <= 0 || m0 < 0 || m < 0) {
			throw new RuntimeException("Error in Federpendel: invalid Constructor Arguments...check input");
		}
		this.N = N;
		this.step = step;
		this.finalTime = finalTime;
		this.stepCount = (int) (finalTime / step);
		this.E = E;
		this.A = A;
		this.L0 = L0;
		this.L_i = L0 / N;
		this.k = this.A * this.E / this.L_i;
		this.m0 = m0;
		this.m = m;
		this.m_i = m0 / N;

		this.v0 = null;
		this.dx0 = null;
		this.dx = null;
		this.v = null;
	}

	public void setInitialPosition(InitialPosition dx0) {
		this.dx0 = dx0;
	}

	public void setInitialVelocity(InitialVelocity v0) {
		this.v0 = v0;
	}

	public void propagate() {
		if (!initialConditionsValid()) {
			throw new RuntimeException("initial conditions not valid in propagate() of " + this);
		}
		double[] initDX = dx0.getInitialDX(this); // test
		double[] initV = v0.getInitalVelocity(this);

		this.dx = new double[stepCount][N + 1]; // test
		this.v = new double[stepCount][N + 1];
		for (int i = 0; i < initDX.length; i++) {
			this.dx[0][i] = initDX[i];
			this.v[0][i] = initV[i];
		}
		for (int t = 1; t < stepCount; t++) {
			// #######Velocity field############
			for (int i = 0; i < N; i++) {
				// Force
				double deformation = -dx[t - 1][i] + dx[t - 1][i + 1];
				double forceSpring = k * deformation; // test
				double accel = ((forceSpring) / m_i + GRAVITATIONAL_ACCELERATION);
				v[t][i] = (accel-DAMPING*v[t-1][i]) * step + v[t - 1][i];
			}
			// last point, only one spring
			double forceSpring = -k * dx[t - 1][N];// test
			double accel = (forceSpring / m + GRAVITATIONAL_ACCELERATION);
			v[t][N] = (accel-DAMPING*v[t-1][N]) * step + v[t - 1][N];

			// position field
			dx[t][0] = v[t - 1][0] * step + dx[t - 1][0];
			for (int i = 1; i < dx[t].length; i++) {
				dx[t][i] = (+v[t][i] - v[t][i - 1]) * step + dx[t - 1][i];
			}
		}
		// printDX();
		System.out.println();
		printV();
	}

	private void printDX() {
		for (int t = 0; t < dx.length; t++) {
			if (printCondition(t)) {
				System.out.print(t * step);
				for (int i = 0; i < dx[t].length; i++) {
					System.out.print("\t" + dx[t][i]);
				}
				System.out.println();
			}

		}
	}

	private void printV() {
		for (int t = 0; t < v.length; t++) {
			if (printCondition(t)) {
				System.out.print(t * step);
				for (int i = 0; i < v[t].length; i++) {
					if (printCondition2(i)) {
						System.out.print("\t" + v[t][i]);
					}
				}
				System.out.println();
			}
		}
	}

	private boolean printCondition(int t) {
		if (t % 100 == 0) {
			return true;
		}
		return false;
	}

	private boolean printCondition2(int i) {
		if (i == 10 || i == 5  || i==1) {
			return true;
		}
		return false;
	}

	/**
	 * 
	 * @param i
	 *            position in the spring
	 * @return the gravitational force pulling at the element i /N
	 */
	public double getGravForce(int i) {
		double massPulling = (N - i) * m_i + m;
		return massPulling * GRAVITATIONAL_ACCELERATION;
	}

	public boolean paramsAreValid() {
		if (N <= 0 || step <= 0 || stepCount <= 0 || E <= 0 || A <= 0 || L0 <= 0 || L_i <= 0 || m0 < 0 || m_i <= 0
				|| m < 0) {
			return false;
		}
		return true;
	}

	public boolean initialConditionsValid() {
		if (v0 == null || dx0 == null) {
			return false;
		}
		return true;
	}

	public boolean trajectoryIsValid() {
		if (dx == null || v == null) {
			return false;
		} else if (dx.length != stepCount || v.length != stepCount) {
			return false;
		}
		return true;
	}
}
